﻿using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Transactions;

namespace O1.Kernel
{
    [ContractVerification(true)]
    internal class TransactionToken
    {
        public static readonly TransactionToken UncommittedRead = new TransactionToken(TransactionExtensions.MaxTransactionId, IsolationLevel.ReadUncommitted);

        private readonly TransactionToken outerScope;

        private List<TransactionNotice> notices;

        public TransactionToken(ulong transactionId, IsolationLevel isolation)
        {
            this.TransactionId = transactionId;
            this.Isolation = isolation;
        }

        public TransactionToken(ulong transactionId, IsolationLevel isolation, TransactionToken outerScope)
            : this(transactionId, isolation)
        {
            this.outerScope = outerScope;
        }

        public ulong TransactionId { get; private set; }

        public IsolationLevel Isolation { get; private set; }

        public bool HasNotices
        {
            get
            {
                return this.notices != null;
            }
        }

        public bool IsWriteVisible(ulong encodedTransactionId)
        {
            var comparand = encodedTransactionId.ToId();
            if (this.TransactionId < comparand)
            {
                return false;
            }

            if (encodedTransactionId.Status() == DmlStatus.Committed)
            {
                return true;
            }

            // Can't write volitile data
            if (this.Isolation == IsolationLevel.RepeatableRead ||
            this.Isolation == IsolationLevel.Serializable)
            {
                return this.InScope(comparand);
            }
            
            // Can write volitile data
            return true;
        }

        public bool IsReadVisible(ulong otherTransactionId)
        {
            /*
             * If the other TX is after this TX, it isn't visible.
             * If the other TX is committed and less than this, and not deleted, it is visible.
             * If the TX is not committed, and the IsolationLevel is read committed, it is not visible except to this scope.
             *  (other isolation levels vary the writeability of the volitile data)
             * If this TX is less than the other, it is not visible unless part of the same scope.
             * 
             */
            var comparand = otherTransactionId.ToId();
            if (comparand > this.TransactionId)
            {
                return false;
            }

            if (otherTransactionId.Status() == DmlStatus.Committed)
            {
                return otherTransactionId.Operation() != DmlOperation.Delete;
            }

            if (this.Isolation == IsolationLevel.ReadCommitted)
            {
                return this.InScope(comparand);
            }

            return true;
        }

        public void AddNotice(ulong otherTransId, int reasonCode, int cellId)
        {
            if (this.notices == null)
            {
                this.notices = new List<TransactionNotice>();
            }

            this.notices.Add(new TransactionNotice(otherTransId, reasonCode, cellId));
        }

        public TransactionNotice[] GetNotices()
        {
            if (this.notices == null)
            {
                return new TransactionNotice[0];
            }

            return this.notices.ToArray();
        }

        private bool InScope(ulong comparand)
        {
            var outer = this;
            while (outer != null)
            {
                if (outer.TransactionId == comparand)
                {
                    return true;
                }

                outer = outer.outerScope;
            }

            return false;
        }
    }
}
